package com.wdy.golden.tinysql.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.util.Assert;

import com.wdy.golden.tinysql.constant.TinysqlConstants;
import com.wdy.golden.tinysql.enmus.LikeType;
import com.wdy.golden.tinysql.enmus.SqlSeparateType;
import com.wdy.golden.tinysql.enmus.TinysqlSchema;
import com.wdy.golden.tinysql.enmus.XmlNodeType;
import com.wdy.golden.tinysql.exception.TinySqlException.FileException;
import com.wdy.golden.tinysql.mapping.Inf;
import com.wdy.golden.tinysql.mapping.Result;
import com.wdy.golden.tinysql.mapping.ResultMap;
import com.wdy.golden.tinysql.mapping.Script;
import com.wdy.golden.tinysql.mapping.ScriptContent;
import com.wdy.golden.tinysql.mapping.SqlScript;
import com.wdy.golden.tinysql.mapping.Tinysql;

/**
 * @author Dongye_Wu
 * @version: 1.0.0
 * @Description: TODO
 * @date 2020年4月9日 下午9:01:52
 */
public class MappingUtil {
	private static final Logger log = LoggerFactory.getLogger(MappingUtil.class);
	
	public final static String WHERE_SQL = " where 1=1 ";
	public final static String ORDERBY_SQL = " order by ";
	
	/**
	 * 只读取文件夹下的文件，不读取子文件夹及子文件夹中的文件
	 * @param dirPath
	 * @return
	 * @throws Exception
	 */
	public static List<File> getMappingFiles(String mappingFilePattern) throws FileException{
		List<File> files = null;
		ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
		
		try {
			Resource[] resources = resourceLoader.getResources(mappingFilePattern);
			if(resources!=null && resources.length>0) {
				files = new ArrayList<File>();
				for(Resource resource : resources) {
					InputStream inputStream = resource.getInputStream();
					String[] fileNameArr = resource.getFilename().split("\\.");
					File tmpFile = File.createTempFile(fileNameArr[0], "."+fileNameArr[1]);
					FileUtils.copyInputStreamToFile(inputStream, tmpFile);
					files.add(tmpFile);
				}
			}
		} catch (IOException e) {
			log.warn("[Tinysql] When get mapping file path, IOException occured: {}", e.getMessage());
		}
		return files;
		
	}

	public static Document load(File file) {
		Document document = null;
		try {
			SAXReader saxReader = new SAXReader();
			document = saxReader.read(file);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return document;
	}

	
	public static Inf getInf(Element tinysqlChildEl) {
		Inf inf = new Inf();
		inf.setName(
			tinysqlChildEl.attributeValue(TinysqlSchema.INTERFACE_ATTR_NAME.getName())
		);
		return inf;
	}
	
	public static ResultMap getResultMap(Element tinysqlChildEl) {
		ResultMap resultMap = new ResultMap();
		
		resultMap.setName(
			tinysqlChildEl.attributeValue(TinysqlSchema.RESULTMAP_ATTR_NAME.getName())
		);

		List<Result> results = null;
		if(tinysqlChildEl.hasContent()) {
			results = new ArrayList<Result>();
			for(Iterator<Element> j = tinysqlChildEl.elementIterator(); j.hasNext();) {
				Element resultEl = j.next();
				if(
					TinysqlSchema.RESULTMAP_CHILD_RESULT.getName().equalsIgnoreCase(resultEl.getName())
				) {
					Result result = new Result();
					result.setColumn(
						resultEl.attributeValue(TinysqlSchema.MAPPING_ATTR_COLUMN.getName())
					);
					result.setProperty(
						resultEl.attributeValue(TinysqlSchema.MAPPING_ATTR_PROPERTY.getName())
					);
					result.setSeparator(
						resultEl.attributeValue(TinysqlSchema.MAPPING_ATTR_SEPARATOR.getName())
					);
					results.add(result);
				}
			}
		}
		
		resultMap.setResults(results);
		
		return resultMap;
	}

	public static Script getScript(Element tinysqlChildEl) {
		Script script = new Script();
		script.setMethod(
			tinysqlChildEl.attributeValue(TinysqlSchema.SCRIPT_ATTR_METHOD.getName())
		);
		script.setResultMapRef(
			tinysqlChildEl.attributeValue(TinysqlSchema.SCRIPT_ATTR_RESULTMAPREF.getName())
		);

		if(tinysqlChildEl.hasContent()) {
			List<ScriptContent> contents = new ArrayList<ScriptContent>();
			if(tinysqlChildEl.isTextOnly()) {
				script.setNodeType(XmlNodeType.TEXT);
				script.setSqlScript(tinysqlChildEl.getText());
			}else {
				script.setNodeType(XmlNodeType.ELEMENT);
				
				List<Node> list = tinysqlChildEl.content();
				if(list!=null && list.size()>0) {
					Iterator<Node> iterator = list.iterator();
					while(iterator.hasNext()){
						ScriptContent scriptContent = new ScriptContent();
						Node node = (Node)iterator.next();
						if(Node.ELEMENT_NODE == node.getNodeType()) {
							if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_WHERE.getName())) {
								scriptContent.setWhere(true);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_NVLIGNORE.getName())) {
								scriptContent.setNvlIgnore(true);
								Element nvlIgnoreElement = (Element)node;							
								scriptContent.setParams(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLIGNORE_ATTR_PARAMS.getName())
								);
								scriptContent.setSeparate(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLIGNORE_ATTR_SEPARATE.getName())
								);
								scriptContent.setContent(node.getText());
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_CONDITION.getName())) {
								scriptContent.setCondition(true);
								Element conditionElement = (Element)node;								
								scriptContent.setParam(
										conditionElement.attributeValue(TinysqlSchema.CONDITION_ATTR_PARAM.getName())
								);
								scriptContent.setValue(
										conditionElement.attributeValue(TinysqlSchema.CONDITION_ATTR_VALUE.getName())
								);
								scriptContent.setContent(node.getText());
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_ORDERBY.getName())) {
								scriptContent.setOrderBy(true);
								Element orderByElement = (Element)node;								
								scriptContent.setParam(
										orderByElement.attributeValue(TinysqlSchema.ORDERBY_ATTR_PARAM.getName())
								);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_LIKE.getName())) {
								scriptContent.setLike(true);
								Element likeElement = (Element)node;
								scriptContent.setParam(
										likeElement.attributeValue(TinysqlSchema.LIKE_ATTR_PARAM.getName())
								);
								scriptContent.setType(
										likeElement.attributeValue(TinysqlSchema.LIKE_ATTR_TYPE.getName())
								);
								scriptContent.setSeparate(
										likeElement.attributeValue(TinysqlSchema.LIKE_ATTR_SEPARATE.getName())
								);
								scriptContent.setContent(node.getText());
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_PAGINATION.getName())) {
								scriptContent.setPagination(true);
								Element paginationElement = (Element)node;
								scriptContent.setRowStartParam(
										paginationElement.attributeValue(TinysqlSchema.PAGINATION_ATTR_ROWSTARTPARAM.getName())
								);
								scriptContent.setRowEndParam(
										paginationElement.attributeValue(TinysqlSchema.PAGINATION_ATTR_ROWENDPARAM.getName())
								);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_SUPERPAGINATION.getName())) {
								scriptContent.setSuperPagination(true);
								Element superPaginationElement = (Element)node;
								scriptContent.setRowStartParam(
										superPaginationElement.attributeValue(TinysqlSchema.PAGINATION_ATTR_ROWSTARTPARAM.getName())
								);
								scriptContent.setRowEndParam(
										superPaginationElement.attributeValue(TinysqlSchema.PAGINATION_ATTR_ROWENDPARAM.getName())
								);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_PARAMIN.getName())) {
								scriptContent.setParamIn(true);
								Element paramInElement = (Element)node;
								scriptContent.setName(
										paramInElement.attributeValue(TinysqlSchema.PARAMIN_ATTR_NAME.getName())
								);
								scriptContent.setParam(
										paramInElement.attributeValue(TinysqlSchema.PARAMIN_ATTR_PARAM.getName())
								);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_BATCHPARAMS.getName())) {
								scriptContent.setBatchParams(true);
								Element paramInElement = (Element)node;
								scriptContent.setParams(
										paramInElement.attributeValue(TinysqlSchema.BATCHPARAMS_ATTR_PARAMS.getName())
								);
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_NVLLISTIGNORE.getName())) {
								scriptContent.setNvlListIgnore(true);
								Element nvlIgnoreElement = (Element)node;							
								scriptContent.setParams(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLLISTIGNORE_ATTR_PARAMS.getName())
								);
								scriptContent.setSeparate(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLLISTIGNORE_ATTR_SEPARATE.getName())
								);
								scriptContent.setContent(node.getText());
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_NVLEXEC.getName())) {
								scriptContent.setNvlExec(true);;
								Element nvlIgnoreElement = (Element)node;							
								scriptContent.setParams(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLEXEC_ATTR_PARAMS.getName())
								);
								scriptContent.setSeparate(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLEXEC_ATTR_SEPARATE.getName())
								);
								scriptContent.setContent(node.getText());
							}else if(node.getName().equalsIgnoreCase(TinysqlSchema.SCRIPT_CHILD_NVLLISTEXEC.getName())) {
								scriptContent.setNvlListExec(true);;
								Element nvlIgnoreElement = (Element)node;							
								scriptContent.setParams(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLLISTEXEC_ATTR_PARAMS.getName())
								);
								scriptContent.setSeparate(
									nvlIgnoreElement.attributeValue(TinysqlSchema.NVLLISTEXEC_ATTR_SEPARATE.getName())
								);
								scriptContent.setContent(node.getText());
							}
							
						}else if(Node.TEXT_NODE == node.getNodeType()) {
							scriptContent.setContent(node.getText());
						}
						contents.add(scriptContent);
					}
				}
			}
			if(contents.size()>0) {
				script.setContents(contents);
			}
		}		
		return script;
	}
	
	public static Tinysql readMappingFile(File file) {
		Document dom = load(file);
		Element rootEl = dom.getRootElement();
		
		Tinysql tinysql = null;
		if(rootEl.hasContent()) {
			tinysql = new Tinysql();
			List<ResultMap> resultMaps = new ArrayList<ResultMap>();
			List<Script> scripts = new ArrayList<Script>();
			Inf inf = null;
			for (Iterator<Element> i = rootEl.elementIterator(); i.hasNext();) {
				Element tinysqlChildEl = i.next();				
				if (
					TinysqlSchema.INTERFACE.getName().equalsIgnoreCase(tinysqlChildEl.getName())
				) {
					inf = getInf(tinysqlChildEl);
				} else if (
					TinysqlSchema.RESULTMAP.getName().equalsIgnoreCase(tinysqlChildEl.getName())
				) {
					resultMaps.add(getResultMap(tinysqlChildEl));
				} else if (
					TinysqlSchema.SCRIPT.getName().equalsIgnoreCase(tinysqlChildEl.getName())
				) {
					scripts.add(getScript(tinysqlChildEl));
				}
			}

			Assert.notNull(inf, "[Tinysql] mapping xml, must has a interface tag.");
			
			if(resultMaps.size()>0) {
				tinysql.setResultMaps(resultMaps);
			}
			if(scripts.size()>0) {
				tinysql.setScripts(scripts);
			}
			tinysql.setInf(inf);
		}
		return tinysql;
	}
	
	public static String getMapKey(String... keys) {
		String mapKey = null;
		StringBuffer sb = new StringBuffer();
		for(String key : keys) {
			sb.append(key+"|");
		}
		mapKey = sb.toString();
		if(StringUtil.isNotEmpty(mapKey)) {
			mapKey = mapKey.substring(0, mapKey.length()-1);
		}
		return mapKey;
	}
	
	public static SqlScript getSqlScript(Script script, Map<String, Object> argMap) {
		if(script==null) {
			return null;
		}
		
		boolean superPaginationFlag = false;
		SqlScript sqlScript = new SqlScript();
		
		if(XmlNodeType.TEXT == script.getNodeType()) {
			sqlScript.setOriginSql(StringUtil.trim(script.getSqlScript()));
		}else if(XmlNodeType.ELEMENT == script.getNodeType()) {
			List<ScriptContent> scriptContentList = script.getContents();
			StringBuffer scriptSb = null;
			if(scriptContentList!=null && scriptContentList.size()>0) {
				scriptSb = new StringBuffer();
				
				boolean paginationFlag = false;
				String rowStartParam = null;
				String rowEndParam = null;
				MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
				
				for(ScriptContent scriptContent : scriptContentList) {
					if(scriptContent.isNvlIgnore()) {
						String params = scriptContent.getParams();
						if(StringUtil.isEmpty(params)) {
							continue;
						}
						String[] paramList = params.split(",");
						
						if(MapUtil.containsAllKeyAndNotNull(argMap, paramList)) {
							String sep = SqlSeparateType.valueOf(scriptContent.getSeparate()).getName();
							
							scriptSb.append(
									sep + 
									TinysqlConstants.BLANK + 
									scriptContent.getContent() + 
									TinysqlConstants.BLANK
							);
						}
						
					}
					else if(scriptContent.isWhere()){
						scriptSb.append(WHERE_SQL);
					}
					else if(scriptContent.isCondition()){
						if(argMap.containsKey(scriptContent.getParam())) {
							boolean match = false;
							List<String> vList = new ArrayList<String>();
							if(StringUtil.isNotEmpty(scriptContent.getValue())) {
								vList = Arrays.asList(scriptContent.getValue().split(","));
							}
							if(vList!=null && vList.size()>0) {
								for(String vl : vList) {
									if(StringUtil.equalsIgnoreCase((String)argMap.get(scriptContent.getParam()), vl)) {
										match = true;
										break;
									}
								}
							}							
							if(match) {
								scriptSb.append(
										TinysqlConstants.BLANK + 
										scriptContent.getContent() + 
										TinysqlConstants.BLANK
								);
							}
						}
					}
					else if(scriptContent.isOrderBy()){
						Map<String, Object> orderByMap =MapUtil.containsStart(argMap, scriptContent.getParam());
						if(orderByMap!=null && !orderByMap.isEmpty()) {
							scriptSb.append(ORDERBY_SQL);
							for(Map.Entry<String, Object> paramsEntry : orderByMap.entrySet()){
								scriptSb.append(
										StringUtil.removeStart(paramsEntry.getKey(), scriptContent.getParam()+TinysqlConstants.POINT) + 
										TinysqlConstants.BLANK +
										paramsEntry.getValue() + 
										TinysqlConstants.COMMA
								);
							}
							scriptSb = StringUtil.removeEnd(scriptSb, TinysqlConstants.COMMA);
						}
					}
					else if(scriptContent.isLike()){
						if(argMap.containsKey(scriptContent.getParam())) {
							String paramValue = (String)argMap.get(scriptContent.getParam());
							if(paramValue!=null) {
								String sep = SqlSeparateType.valueOf(scriptContent.getSeparate()).getName();
								
								if(StringUtil.equals(scriptContent.getType(), LikeType.BOTHLIKE.name)) {
									paramValue = TinysqlUtil.bothLike(paramValue);
								}else if(StringUtil.equals(scriptContent.getType(), LikeType.LEFTLIKE.name)) {
									paramValue = TinysqlUtil.leftLike(paramValue);
								}else if(StringUtil.equals(scriptContent.getType(), LikeType.RIGHTLIKE.name)) {
									paramValue = TinysqlUtil.rightLike(paramValue);
								}
								paramValue = TinysqlConstants.QUOTE + paramValue + TinysqlConstants.QUOTE;
								
								scriptSb.append(
										TinysqlConstants.BLANK +
										sep + 
										TinysqlConstants.BLANK +
										StringUtil.replace(
												scriptContent.getContent(), 
												TinysqlConstants.COLON+scriptContent.getParam(), 
												paramValue
											) + 
										TinysqlConstants.BLANK
									);
							}
						}
					}
					else if(scriptContent.isPagination()){
						paginationFlag = true;
						rowStartParam = scriptContent.getRowStartParam();
						rowEndParam = scriptContent.getRowEndParam();
					}
					else if(scriptContent.isSuperPagination()){
						superPaginationFlag = true;
						rowStartParam = scriptContent.getRowStartParam();
						rowEndParam = scriptContent.getRowEndParam();
					}
					else if(scriptContent.isParamIn()){
						mapSqlParameterSource.addValue(scriptContent.getName(), argMap.get(scriptContent.getParam()));
					}
					else if(scriptContent.isBatchParams()){
						String batchParams = scriptContent.getParams();
						if(StringUtil.isNotEmpty(batchParams)) {
							String[] batchParamArr = batchParams.split(",");
							sqlScript.setBatchParams(Arrays.asList(batchParamArr));
						}
					}
					else if(scriptContent.isNvlListIgnore()) {
						String params = scriptContent.getParams();
						if(StringUtil.isEmpty(params)) {
							continue;
						}
						String[] paramList = params.split(",");
						
						if(MapUtil.containsAllKeyAndListNotEmpty(argMap, paramList)) {
							String sep = SqlSeparateType.valueOf(scriptContent.getSeparate()).getName();
							scriptSb.append(
									sep + 
									TinysqlConstants.BLANK + 
									scriptContent.getContent() + 
									TinysqlConstants.BLANK
							);
						}
						
					}
					else if(scriptContent.isNvlListExec()) {
						String params = scriptContent.getParams();
						if(StringUtil.isEmpty(params)) {
							continue;
						}
						String[] paramList = params.split(",");
						
						if(MapUtil.listEmpty(argMap, paramList)) {
							String sep = SqlSeparateType.valueOf(scriptContent.getSeparate()).getName();
							scriptSb.append(
									sep + 
									TinysqlConstants.BLANK + 
									scriptContent.getContent() + 
									TinysqlConstants.BLANK
							);
						}
						
					}
					else if(scriptContent.isNvlExec()) {
						String params = scriptContent.getParams();
						if(StringUtil.isEmpty(params)) {
							continue;
						}
						String[] paramList = params.split(",");
						
						if(MapUtil.valueEmpty(argMap, paramList)) {
							String sep = SqlSeparateType.valueOf(scriptContent.getSeparate()).getName();
							scriptSb.append(
									sep + 
									TinysqlConstants.BLANK + 
									scriptContent.getContent() + 
									TinysqlConstants.BLANK
							);
						}
						
					}
					else {
						scriptSb.append(scriptContent.getContent());
					}
				}
				
				//add pagination sql
				if(paginationFlag) {
					//
					scriptSb.insert(0, TinysqlConstants.PAGINATION_PREFIX);
					//
					String rowStartParamValue = (String)argMap.get(rowStartParam);
					String rowEndParamValue = (String)argMap.get(rowEndParam);
					scriptSb.append(String.format(TinysqlConstants.PAGINATION_SUFFIX, rowEndParamValue, rowStartParamValue));
				}
				
				//OriginSql
				sqlScript.setOriginSql(StringUtil.trim(scriptSb.toString()));
				//SqlParamSource
				if(mapSqlParameterSource.getParameterNames().length>0) {
					sqlScript.setSqlParamSource(mapSqlParameterSource);
				}
				
				//add super pagination sql
				if(superPaginationFlag) {
					String rowStartParamValue = (String)argMap.get(rowStartParam);
					String rowEndParamValue = (String)argMap.get(rowEndParam);
					
					StringBuffer superPageSb = new StringBuffer(scriptSb.toString());
					superPageSb.insert(0, TinysqlConstants.PAGINATION_PREFIX);
					superPageSb.append(String.format(TinysqlConstants.PAGINATION_SUFFIX, rowEndParamValue, rowStartParamValue));
					sqlScript.setPaginationSql(StringUtil.trim(superPageSb.toString()));
					
					StringBuffer countSb = new StringBuffer(scriptSb.toString());
					countSb.insert(0, TinysqlConstants.COUNT_PREFIX);
					countSb.append(TinysqlConstants.COUNT_SUFFIX);
					sqlScript.setCountSql(StringUtil.trim(countSb.toString()));
					
					sqlScript.setSuperPagination(true);
				}
			}
		}
		return sqlScript;
	}
}